
using Irony.Compiler;

using IronLua.Ast;

using Microsoft.Scripting.Runtime;

namespace IronLua.Parser {

	public class LuaGrammar : Grammar {

		public LuaGrammar ()
		{
			var constants = new ConstantSetTerminal ();
			constants.Add ("true", RuntimeHelpers.True);
			constants.Add ("false", RuntimeHelpers.False);
			constants.Add ("nil", null);

			ExtraTerminals.Add (constants);

			ExtraTerminals.Add (new CommentTerminal ("comment", "--", "--[[", "]]"));

			var name = new IdentifierTerminal ("Name");

			var number = new NumberTerminal ("Number");
			var dblqstr = new StringLiteral ("DblQString");
			var sqstr = new StringLiteral ("SQString", "'", "'");

			var str = new NonTerminal ("String");
			var literal = new NonTerminal ("literal", typeof (LiteralExpression));

			var chunk = new NonTerminal ("chunk", typeof (BlockStatement));
			var block = new NonTerminal ("block");

			var stat = new NonTerminal ("stat");
			var assign_stat = new NonTerminal ("assign_stat", typeof (AssignStatement));
			var do_stat = new NonTerminal ("do_stat", typeof (DoStatement));
			var while_stat = new NonTerminal ("while_stat", typeof (WhileStatement));
			var repeat_stat = new NonTerminal ("repeat_stat", typeof (RepeatStatement));
			var if_stat = new NonTerminal ("if_stat", typeof (IfStatement));
			var elseif_clause = new NonTerminal ("elseif_clause", typeof (ElseIfClause));
			var for_stat = new NonTerminal ("for_stat", typeof (ForStatement));
			var foreach_stat = new NonTerminal ("foreach_stat", typeof (ForEachStatement));
			var function_stat = new NonTerminal ("function_stat", typeof (FunctionStatement));
			var method_stat = new NonTerminal ("method_stat", typeof (MethodStatement));
			var local_func_stat = new NonTerminal ("local_func_stat", typeof (LocalFunctionStatement));
			var local_stat = new NonTerminal ("local_stat", typeof (LocalStatement));

			var laststat = new NonTerminal ("laststat");
			var return_stat = new NonTerminal ("return_stat", typeof (ReturnStatement));
			var break_stat = new NonTerminal ("break_stat", typeof (BreakStatement));

			var funcname = new NonTerminal ("funcname");
			var varlist = new NonTerminal ("varlist");
			var var = new NonTerminal ("var");
			var slot = new NonTerminal ("slot", typeof (SlotExpression));
			var member = new NonTerminal ("member", typeof (MemberExpression));
			var namelist = new NonTerminal ("namelist");
			var explist = new NonTerminal ("explist");

			var exp = new NonTerminal ("exp");
			var unary_exp = new NonTerminal ("unary_exp", typeof (UnaryExpression));
			var binary_exp = new NonTerminal ("binary_exp", typeof (BinaryExpression));

			var prefixexp = new NonTerminal ("prefixexp");
			var call = new NonTerminal ("call");
			var functioncall = new NonTerminal ("functioncall", typeof (FunctionCallExpression));
			var methodcall = new NonTerminal ("methodcall", typeof (MethodCallExpression));
			var args = new NonTerminal ("args");
			var function = new NonTerminal ("function", typeof (FunctionExpression));
			var funcbody = new NonTerminal ("funcbody");
			var vararg = new NonTerminal ("vararg", typeof (VarArgExpression));
			var parlist = new NonTerminal ("parlist");
			var tableconstructor = new NonTerminal ("tableconstructor", typeof (TableCreationExpression));
			var fieldlist = new NonTerminal ("fieldlist");
			var field = new NonTerminal ("field");
			var fieldsep = new NonTerminal ("fieldsep");
			var binop = new NonTerminal ("binop");
			var unop = new NonTerminal ("unop");

			str.Rule = dblqstr | sqstr;
			literal.Rule = Symbol ("nil") | Symbol ("true") | Symbol ("false") | number | str;

			chunk.Rule = WithStar (stat + WithQ (Symbol (";"))) + WithQ (laststat + WithQ (Symbol (";")));

			block.Rule = chunk;

			elseif_clause.Rule = Symbol ("elseif") + exp + "then" + block;

			assign_stat.Rule = varlist + "=" + explist;
			do_stat.Rule = Symbol ("do") + block + "end";
			while_stat.Rule = Symbol ("while") + exp + "do" + block + "end";
			repeat_stat.Rule = Symbol ("repeat") + block + "until" + exp;
			if_stat.Rule = Symbol ("if") + exp + "then" + block + WithStar (elseif_clause) + WithQ (Symbol ("else") + block) + "end";
			for_stat.Rule = Symbol ("for") + name + "=" + exp + "," + exp + WithQ (Symbol (",") + exp) + "do" + block + "end";
			foreach_stat.Rule = Symbol ("for") + namelist + "in" + explist + "do" + block + "end";
			function_stat.Rule = Symbol ("function") + funcname + funcbody;
			method_stat.Rule = Symbol ("function") + funcname + ":" + name + funcbody;
			local_func_stat.Rule = Symbol ("local") + "function" + name + funcbody;
			local_stat.Rule = Symbol ("local") + namelist + WithQ (Symbol ("=") + explist);

			stat.Rule = assign_stat |
				call |
				do_stat |
				while_stat |
				repeat_stat |
				if_stat |
				for_stat |
				foreach_stat |
				function_stat |
				method_stat |
				local_func_stat |
				local_stat;

			return_stat.Rule = Symbol ("return") + WithQ (explist);
			break_stat.Rule = Symbol ("break");

			laststat.Rule =  return_stat | break_stat;

			funcname.Rule = name + WithStar (Symbol (".") + name);

			varlist.Rule = var + WithStar (Symbol (",") + var);

			slot.Rule = prefixexp + "[" + exp + "]";
			member.Rule = prefixexp + "." + name;

			var.Rule = name | slot | member;

			namelist.Rule = name + WithStar (Symbol (",") + name);

			explist.Rule = WithStar (exp + ",") + exp;

			unary_exp.Rule = unop + exp;
			binary_exp.Rule = exp + binop + exp;
			function.Rule = Symbol ("function") + funcbody;

			exp.Rule = literal | function | vararg |
				prefixexp | tableconstructor | binary_exp | unary_exp;

			prefixexp.Rule = var | call  | "(" + exp + ")";

			call.Rule = functioncall | methodcall;

			functioncall.Rule = prefixexp + args;
			methodcall.Rule = prefixexp + ":" + name + args;

			args.Rule = Symbol ("(") + WithQ (explist) + ")" | tableconstructor | str;

			funcbody.Rule = Symbol ("(") + WithQ (parlist) + ")" + block + "end";

			vararg.Rule = Symbol ("...");

			parlist.Rule = name + WithStar (Symbol (",") + name) + WithQ (Symbol (",") + vararg) | vararg;

			tableconstructor.Rule = Symbol ("{") + WithQ (fieldlist) + "}";

			fieldlist.Rule = field + WithStar (fieldsep + field) + WithQ (fieldsep);

			field.Rule = Symbol ("[") + exp + "]" + "=" + exp | name + "=" + exp | exp;

			fieldsep.Rule = Symbol (",") | ";";

			binop.Rule = Symbol ("+") | "-" | "*" | "/" | "^" | "%" | ".." |
				"<" | "<=" | ">" | ">=" | "==" | "~=" | "or" | "and";

			unop.Rule = Symbol ("-") | "not" | "#";

			Root = chunk;

			RegisterOperators (1, "or");
			RegisterOperators (2, "and");
			RegisterOperators (3, "<", ">", "<=", ">=", "~=", "==");
			RegisterOperators (4, "..");
			RegisterOperators (5, "+", "-");
			RegisterOperators (6, "*", "/", "%");
			RegisterOperators (7, "not", "#");
			RegisterOperators (8, "^");

			RegisterPunctuation ("(", ")", ",", ";");
		}
	}
}
